package com.basker.pisces.core.iterate;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Stack;

import org.springframework.util.CollectionUtils;

import com.basker.pisces.core.meta.ICollectionField;
import com.basker.pisces.core.meta.IDataObjectField;
import com.basker.pisces.core.meta.IDataObjectMeta;

public class DataObjectIterator {

    private Object[] dataObjects;
    private IDataObjectMeta objectMeta;
    private IDataObjectIterateHandler handler;

    public DataObjectIterator(Object[] dataObjects, IDataObjectMeta objectMeta, IDataObjectIterateHandler handler) {
        this.dataObjects = dataObjects;
        this.objectMeta = objectMeta;
        this.handler = handler;
    }

    public void iterate() {
        Stack<String> iteratePath = new Stack<>();
        iteratePath.push(this.objectMeta.getName());

        this.iterate(null, Arrays.asList(this.dataObjects), this.objectMeta, iteratePath);

        iteratePath.pop();
    }

    private void iterate(List<Object> parentObjects, List<Object> dataObjects, IDataObjectMeta currentObjectMeta,
                         Stack<String> iteratePath) {
        if (CollectionUtils.isEmpty(dataObjects)) {
            return;
        }

        if (this.handler == null) {
            return;
        }

        DataObjectIterateEvent event = new DataObjectIterateEvent(parentObjects, dataObjects, currentObjectMeta,
                iteratePath);
        this.handler.handleDataObjects(event);

        this.iterateObjectFields(dataObjects, currentObjectMeta, iteratePath);
        this.iterateCollectionFields(dataObjects, currentObjectMeta, iteratePath);
    }

    private void iterateCollectionFields(List<Object> dataObjects, IDataObjectMeta currentObjectMeta,
                                         Stack<String> iteratePath) {
        for (ICollectionField collectionField : currentObjectMeta.getCollectionFields()) {
            int totalSize = dataObjects.stream().map(o -> collectionField.getValue(o).size()).reduce(Integer::sum)
                    .get();

            List<Object> allChildObjects = new ArrayList<Object>(totalSize);
            List<Object> allParentObjects = new ArrayList<Object>(totalSize);

            for (Object parentObj : dataObjects) {
                Collection<Object> childObjs = collectionField.getValue(parentObj);

                allChildObjects.addAll(childObjs);

                for (int i = 0; i < childObjs.size(); i++) {
                    allParentObjects.add(parentObj);
                }

                DataCollectionIterateEvent event = new DataCollectionIterateEvent(collectionField, parentObj,
                        iteratePath);

                this.handler.handleDataCollection(event);
            }

            try {
                iteratePath.push(collectionField.getName());

                this.iterate(allParentObjects, allChildObjects, collectionField.getItemObjectMeta(), iteratePath);
            } finally {
                iteratePath.pop();
            }

        }
    }

    private void iterateObjectFields(List<Object> dataObjects, IDataObjectMeta currentObjectMeta,
                                     Stack<String> iteratePath) {
        int size = dataObjects.size();
        Collection<IDataObjectField> dataObjectFields = currentObjectMeta.getDataObjectFields();

        for (IDataObjectField dataObjectField : dataObjectFields) {
            if (!this.handler.iterateDataObjectField(dataObjectField)) {
                continue;
            }

            List<Object> fieldObjects = new ArrayList<Object>(size);
            List<Object> hostObjects = new ArrayList<Object>(size);

            for (Object dataObject : dataObjects) {
                Object obj = dataObjectField.getValue(dataObject);
                if (obj != null) {
                    fieldObjects.add(obj);
                    hostObjects.add(dataObject);
                }
            }

            try {
                iteratePath.push(dataObjectField.getName());

                this.iterate(hostObjects, fieldObjects, dataObjectField.getDataObjectMeta(), iteratePath);
            } finally {
                iteratePath.pop();
            }

        }
    }

}
